#include "odn_gfx_TextureHandler.h"

//Standard library headers
#include <iostream>
#include <cstdio>
#include <cmath>

//Create and load a new texture from a file
GLuint ouden::gfx::ODN_TextureHandler::createTexture(char* Filename)
{
	
	//TODO: Possibly check file extension and call appropriate load function 
	
	//Load a texture from the image file
	GLuint i = loadTGATexture(Filename);
	
	//Set texture properties 
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	
	//Add a new texture entry to the array 
	textures.push_back(Texture(Filename, i));
	
	//Return the texture index
	return i;
	
}

GLuint ouden::gfx::ODN_TextureHandler::loadTGATexture(char* Filename)
{
	
	//Load the TGA image into ImageData
	
	//The file variable
	FILE* TGAFile;

	typedef unsigned char BYTE;
	typedef unsigned short WORD;

	struct TGAHeader{
		BYTE IDLength;		//Length of the image ID
		BYTE ColorMapType;	//0 = no color map, 1 = color map included
		BYTE ImageType;		//0 = no image data, 1 = uncompressed color mappedm, 2 = uncompressed truecolor, 3 = uncompressed black and white, 9 = Run Length Encoded color mapped, 10 = RLE truecolor, 11 = RLE black and white
		struct TGAColorMapSpec{
			WORD FirstEntryIndex;	//Index of first color map entry to use
			WORD Length;			//Number of colors in color map
			BYTE EntrySize;			//Number of bits per color map entry
		} ColorMapSpec;
		struct TGAImageSpec{
			WORD XOrigin;			//Coordinates of lower left corner of image
			//WORD YOrigin;
			WORD Width;				//Width of image in pixels
			WORD Height;			//Height of image in pixels
			BYTE Depth;				//Color depth in bits per pixel
			BYTE Descriptor;		//Special information about image
		} ImageSpec;
	} Header;

	BYTE* Palette;			//Array of palette data
	BYTE* ImageData;		//Array of image data

	//Open the image file
	TGAFile = fopen(Filename, "rb");
	
	//Make sure it worked alright
	if (!TGAFile)
	{
		std::cout << "Could not load TGA file" << Filename << std::endl;
		return 0;
	}

	//Read in the header
	fread(&Header, sizeof(TGAHeader), 1, TGAFile);

	//Check for read error
	if (ferror(TGAFile)){
	    
		std::cout << "Error reading image header" << std::endl;
		return 0;
		
	}

	//Skip the image ID information
	fseek(TGAFile, Header.IDLength, SEEK_CUR);

	//Check if the image includes a color map
	if (Header.ColorMapType == 1){
	
		//Calculate the length of the palette data in bytes
		//TODO: Check that BYTE is long enough
		BYTE PaletteLength = Header.ColorMapSpec.Length * Header.ColorMapSpec.EntrySize / 8;
	
		//Create the palette array using dynamic memory
		Palette = new BYTE[PaletteLength];
		
		//Read the palette data into Palette
		fread(Palette, sizeof(BYTE), PaletteLength-1, TGAFile);
		
		//Check for read error
		if (ferror(TGAFile)){
		    
			std::cout << "Error reading colour palette" << std::endl;
			return 0;
			
		}	

	}

	//Check if image is RLE encoded
	if (Header.ImageType < 9){		//If not RLE encoded
	    
		//Calculate the length of the image data in bytes
		int ImageLength = Header.ImageSpec.Width * Header.ImageSpec.Height * Header.ImageSpec.Depth / 8;

		//Create the image data array using dynamic memory
		ImageData = new BYTE[ImageLength];

		//Read the image data into ImageData
		fread(ImageData, sizeof(BYTE), ImageLength, TGAFile);

		//Check for read error
		if (ferror(TGAFile)){
		    
			std::cout << "Error reading image data" << std::endl;
			return 0;
			
		}

		//change BGR to RGB (especially for OpenGL later on)
		for (int i = 0; i < ImageLength; i += Header.ImageSpec.Depth / 8)
		{
			//swap blue and red colour value 
			ImageData[i] ^= ImageData[i+2] ^=
				ImageData[i] ^= ImageData[i+2];
		}

		//Flip the image vertically
		//-------------------------

		//Calculate the width of a single scanline
		int ScanLineWidth = Header.ImageSpec.Width * Header.ImageSpec.Depth / 8;

		//Create a new array of dynamic memory to hold a single scanline
		BYTE* ScanLine = new BYTE[ScanLineWidth];

		//Loop through half the scanlines
		for (int i = 0; i < Header.ImageSpec.Height / 2;i++){

			CopyByteArray(ScanLine, ImageData, 0, i * ScanLineWidth, ScanLineWidth);

			CopyByteArray(ImageData, ImageData, i * ScanLineWidth, (Header.ImageSpec.Height - 1 - i) * ScanLineWidth, ScanLineWidth);

			CopyByteArray(ImageData, ScanLine, (Header.ImageSpec.Height - 1 - i) * ScanLineWidth, 0, ScanLineWidth);

		}

		//Delete scanline dynamic memory
		delete [] ScanLine;

		//-------------------------
		
		//Calculate proper sizes for the texture as powers of 2
		int ProperWidth = GetNextPowOf2(Header.ImageSpec.Width);
		int ProperHeight = GetNextPowOf2(Header.ImageSpec.Height);
		
		//Check if image is a power of two in size
		if (ProperWidth != Header.ImageSpec.Width || ProperHeight != Header.ImageSpec.Height){
			
			std::cout << "Texture Warning: " << Filename << ", Image size not power of two" << std::endl;
			
			int ProperScanLineWidth = ProperWidth * Header.ImageSpec.Depth / 8;
	
			int ProperSize = ProperScanLineWidth * ProperHeight;
	
			//Create a new area of memory for the resized image
			BYTE* NewData = new BYTE[ProperSize];
	
			//Zero all of the new data
			for (int i = 0;i<ProperSize;i++)
				NewData[i] = 0;
			
			//Loop through each scanline of image
			for (int i=0;i<Header.ImageSpec.Height;i++){
				
				CopyByteArray(NewData, ImageData, i * ProperScanLineWidth, i * ScanLineWidth, ScanLineWidth);
				
			}
			
			Header.ImageSpec.Width = ProperWidth;
			Header.ImageSpec.Width = ProperHeight;
	
			delete [] ImageData;
	
			ImageData = NewData;
			
		}

	}
	else {	//RLE encoded
		
		//TODO: Add support for RLE encoding 
		
		std::cout << "Error, image is RLE encoded and cannot be loaded" << std::endl;
		return 0;
		
	}
		
	//close TGAFile
	fclose(TGAFile);

	//Now make OpenGL texture using loaded data

    //Temporary texture ID
    GLuint texture_ID;

	//Generate a new OpenGL texture
	glGenTextures(1, &texture_ID);
	
	//Bind the texture
	glBindTexture(GL_TEXTURE_2D, texture_ID);
	
	if (Header.ImageSpec.Depth == 32){
		
		//Build mipmaps for the texture using the raw image data
		gluBuild2DMipmaps(GL_TEXTURE_2D,
		GL_RGBA,
		Header.ImageSpec.Width,
		Header.ImageSpec.Height,
		GL_RGBA,
		GL_UNSIGNED_BYTE,
		ImageData);
		
	}
	else if (Header.ImageSpec.Depth == 24){
		
		//Build mipmaps for the texture using the raw image data
		gluBuild2DMipmaps(GL_TEXTURE_2D,
		GL_RGB,
		Header.ImageSpec.Width,
		Header.ImageSpec.Height,
		GL_RGB,
		GL_UNSIGNED_BYTE,
		ImageData);
		
	}
	
	//Delete the dynamic memory assigned to Palette
	delete [] Palette;
	
	//Delete the dynamic memory allocated to ImageData
	delete [] ImageData;
	
	//Return the texture ID
	return texture_ID;

}

int ouden::gfx::ODN_TextureHandler::CopyByteArray(unsigned char* &DestArray, unsigned char* SourceArray, unsigned int DestStart, unsigned int SourceStart, unsigned int Length)
{

    for (int i=0;i<Length;i++){
		
		DestArray[DestStart + i] = SourceArray[SourceStart + i];
		
	}

}

unsigned short ouden::gfx::ODN_TextureHandler::GetNextPowOf2(unsigned short Number)
{

 	float j;

    for (float i=0;i<10;i++){

		j = std::pow(2, i);

		if (Number <= j)
		    return int(j);
		
	}

	std::cout << "Texture too big" << std::endl;

	return 0;

}
